// @file debug.h This file contains macros and associated helper functions for
// quick cerr oriented debugging that can be quickly enabled and disabled. It
// also contains functions for timing code.
// @author TPOC: contact@palisade-crypto.org
//
// @copyright Copyright (c) 2019, New Jersey Institute of Technology (NJIT)
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution. THIS SOFTWARE IS
// PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef __dbg_h__
#define __dbg_h__

// include <iostream>
// include <cstdlib.h>

/* defining NDEBUG in the compile line turns everything off.
   unless PROFILE is defined in the file before all includes,'
   in which case TIC/TOC will still work and PROFILELOG() can be
   used for logging results to std::cout, and DEBUG() will remain
   silent. dbg_flag does not get used by PROFILELOG()
 */

#include <time.h>
#include <chrono>  // for timing
#include <utility>

#include "utils/inttypes.h"

#if !defined(NDEBUG)

// note that for the following dbg_flag needs to be defined in some scope using
// DEBUG_FLAG
#define DEBUG_FLAG(x) bool dbg_flag = x;

// debugging macro prints value of x on cerr
#define DEBUG(x)                   \
  do {                             \
    if (dbg_flag) {                \
      std::cerr << x << std::endl; \
    }                              \
  } while (0)

// debugging macro prints typography of x and value of x on cerr
#define DEBUGEXP(x)                             \
  do {                                          \
    if (dbg_flag) {                             \
      std::cerr << #x << ":" << x << std::endl; \
    }                                           \
  } while (0)

// debugging macro prints value of x and location in codex on cerr
#define DEBUGWHERE(x)                                               \
  do {                                                              \
    if (dbg_flag) {                                                 \
      std::cerr << #x << ":" << x << " at " << __FILE__ << " line " \
                << __LINE__ << std::endl;                           \
    }                                                               \
  } while (0)

#if defined(PROFILE)  // Profiler works

#define PROFILELOG(x)              \
  do {                             \
    if (true) {                    \
      std::cout << x << std::endl; \
    }                              \
  } while (0)

// debugging macro prints typography of x and value of x on cerr
#define PROFILELOGEXP(x)                        \
  do {                                          \
    if (true) {                                 \
      std::cout << #x << ":" << x << std::endl; \
    }                                           \
  } while (0)

// debugging macro prints value of x and location in codex on cerr
#define PROFILELOGWHERE(x)                                          \
  do {                                                              \
    if (true) {                                                     \
      std::cout << #x << ":" << x << " at " << __FILE__ << " line " \
                << __LINE__ << std::endl;                           \
    }                                                               \
  } while (0)

#else  // #if!defined(PROFILE) // profiling a noop
#define PROFILELOG(x)
#define PROFILELOGEXP(x)
#define PROFILELOGWHERE(x)

#endif  // PROFILE

#define TIC(t) t = timeNow()
#define TOC(t) duration(timeNow() - t)
#define TOC_NS(t) duration_ns(timeNow() - t)
#define TOC_US(t) duration_us(timeNow() - t)
#define TOC_MS(t) duration_ms(timeNow() - t)

#else  // NDEBUG

// #define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__,
// __LINE__, ##__VA_ARGS__)

#if !defined(PROFILE)

// these are turned off functions

#define DEBUG_FLAG(x)
#define DEBUG(x)
#define DEBUGEXP(x)
#define DEBUGWHERE(x)

#define PROFILELOG(x)
#define PROFILELOGEXP(x)
#define PROFILELOGWHERE(x)

#define TIC(t) t = timeNow()
#define TOC(t) std::chrono::steady_clock::duration::zero().count()
#define TOC_NS(t) std::chrono::steady_clock::duration::zero().count()
#define TOC_US(t) std::chrono::steady_clock::duration::zero().count()
#define TOC_MS(t) std::chrono::steady_clock::duration::zero().count()

#else  // PROFILE
// if PROFILE is turned on, then TIC TOC still work and

#define DEBUG_FLAG(x)
#define DEBUG(x)
#define DEBUGEXP(x)
#define DEBUGWHERE(x)

#define PROFILELOG(x)              \
  do {                             \
    if (true) {                    \
      std::cerr << x << std::endl; \
    }                              \
  } while (0)

// debugging macro prints typography of x and value of x on cerr
#define PROFILELOGEXP(x)                        \
  do {                                          \
    if (true) {                                 \
      std::cout << #x << ":" << x << std::endl; \
    }                                           \
  } while (0)

// debugging macro prints value of x and location in codex on cerr
#define PROFILELOGWHERE(x)                                          \
  do {                                                              \
    if (true) {                                                     \
      std::cout << #x << ":" << x << " at " << __FILE__ << " line " \
                << __LINE__ LL std::endl;                           \
    }                                                               \
  } while (0)

#define TIC(t) t = timeNow()
#define TOC(t) duration(timeNow() - t)
#define TOC_NS(t) duration_ns(timeNow() - t)
#define TOC_US(t) duration_us(timeNow() - t)
#define TOC_MS(t) duration_ms(timeNow() - t)

#endif  // PROFILE

#endif  // NDEBUG

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) \
  std::chrono::duration_cast<std::chrono::milliseconds>(a).count()
#define duration_ns(a) \
  std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define duration_us(a) \
  std::chrono::duration_cast<std::chrono::microseconds>(a).count()
#define duration_ms(a) \
  std::chrono::duration_cast<std::chrono::milliseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

double currentDateTime();

template <typename F, typename... Args>
double funcTime(F func, Args&&... args) {
  TimeVar t1 = timeNow();
  func(std::forward<Args>(args)...);
  return duration(timeNow() - t1);
}

#endif  // #__dbg_h__
